<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Iterator</title>
</head>
<body>
<script type="text/javascript">
    {
        /**
         * 遍历器(Iterator)是一种接口，为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口，就可以完成遍历操作(即依次处理该数据结构的所有成员)
         * 作用：
         * 1、为各种数据结构，提供一个统一的、简便的访问接口
         * 2、使得数据结构的成员能够按某种次序排列
         * 3、ES6创造了一种新的遍历命令for...of循环，Iterator接口主要供for...of消费
         */

        /*下面是一个模拟next方法返回值的例子*/
        function makeIterator(array) {
            let nextIndex = 0;
            return {
                next: function () {
                    return nextIndex < array.length ?
                        {value: array[nextIndex++], done: false} :
                        {value: undefined, done: true};
                }
            };
        }

        let it = makeIterator(['a', 'b']);
        console.log(it.next()); // { value: "a", done: false }
        console.log(it.next()); // { value: "b", done: false }
        console.log(it.next()); // { value: undefined, done: true }

        /*next方法返回一个对象，表示当前数据成员的信息
        * 这个对象具有value和done两个属性，value属性返回当前位置的成员，done属性是一个布尔值，表示遍历是否结束，即是否还有必要再一次调用next方法*/

        /*对于遍历器对象来说，done: false和value: undefined属性都是可以省略的，因此上面的makeIterator函数可以简写成下面的形式*/
        /*function makeIterator(array) {
            let nextIndex = 0;
            return {
                next: function() {
                    return nextIndex < array.length ?
                        {value: array[nextIndex++]} :
                        {done: true};
                }
            };
        }*/

        /*下面是一个无限运行的遍历器对象的例子*/
        function idMaker() {
            let index = 0;
            return {
                next: function () {
                    return {value: index++, done: false};
                }
            };
        }

        let iterator = idMaker();
        console.log(iterator.next()); // {value: 0, done: false}
        console.log(iterator.next()); // {value: 1, done: false}
        console.log(iterator.next()); // {value: 2, done: false}

        /*在ES6中，有些数据结构原生具备Iterator接口(比如数组)，即不用任何处理，就可以被for...of循环遍历，有些就不行(比如对象)。
        原因在于，这些数据结构原生部署了Symbol.iterator属性(详见下文)，另外一些数据结构没有。
        凡是部署了Symbol.iterator属性的数据结构，就称为部署了遍历器接口。调用这个接口，就会返回一个遍历器对象。*/
    }
    {
        /**
         * 数据结构的默认Iterator接口
         */
        const obj = {
            [Symbol.iterator]: function () {
                return {
                    next: function () {
                        return {
                            value: 1,
                            done: true
                        };
                    }
                };
            }
        };
        /*上面代码中，对象obj是可遍历的(iterable)，因为具有Symbol.iterator属性。
        执行这个属性，会返回一个遍历器对象。该对象的根本特征就是具有next方法。每次调用next方法，都会返回一个代表当前成员的信息对象，具有value和done两个属性*/
        /*在ES6中，有三类数据结构原生具备Iterator接口：数组、某些类似数组的对象、Set和Map结构*/
        let arr = ['a', 'b', 'c'];
        let iter = arr[Symbol.iterator]();
        console.log(iter.next()); // {value: "a", done: false}
        console.log(iter.next()); // {value: "b", done: false}
        console.log(iter.next()); // {value: "c", done: false}
        console.log(iter.next()); // {value: undefined, done: true}

        /*对象(Object)之所以没有默认部署Iterator接口，是因为对象的哪个属性先遍历，哪个属性后遍历是不确定的，需要开发者手动指定。
        本质上，遍历器是一种线性处理，对于任何非线性的数据结构，部署遍历器接口，就等于部署一种线性转换。*/
        class RangeIterator {
            constructor(start, stop) {
                this.value = start;
                this.stop = stop;
            }

            [Symbol.iterator]() {
                return this;
            }

            next() {
                let value = this.value;
                if (value < this.stop) {
                    this.value++;
                    return {done: false, value: value};
                } else {
                    return {done: true, value: undefined};
                }
            }
        }

        function range(start, stop) {
            return new RangeIterator(start, stop);
        }

        for (const value of range(0, 3)) {
            console.log(value); // 0 1 2
        }

        /*下面是通过遍历器实现指针结构的例子*/
        function Obj(value) {
            this.value = value;
            this.next = null;
        }

        Obj.prototype[Symbol.iterator] = function () {
            let iterator = {
                next: next
            };
            let current = this;

            function next() {
                if (current) {
                    let value = current.value;
                    current = current.next;
                    return {
                        done: false,
                        value: value
                    };
                } else {
                    return {
                        done: true
                    };
                }
            }

            return iterator;
        };

        const one = new Obj(1);
        const two = new Obj(2);
        const three = new Obj(3);
        one.next = two;
        two.next = three;

        for (const x of one) {
            console.log(x); // 1 2 3
        }

        /*下面是另一个为对象添加Iterator接口的例子*/
        /*let obj = {
            data: [ 'hello', 'world' ],
            [Symbol.iterator]() {
                const self = this;
                let index = 0;
                return {
                    next() {
                        if (index < self.data.length) {
                            return {
                                value: self.data[index++],
                                done: false
                            };
                        } else {
                            return { value: undefined, done: true };
                        }
                    }
                };
            }
        };*/

        /*下面是类似数组的对象调用数组的Symbol.iterator方法的例子*/
        let iterable = {
            0: 'a',
            1: 'b',
            2: 'c',
            length: 3,
            [Symbol.iterator]: Array.prototype[Symbol.iterator]
        };
        for (const item of iterable) {
            console.log(item); // a b c
        }

        /*注意，普通对象部署数组的Symbol.iterator方法，并无效果*/
        let iterable2 = {
            a: 'a',
            b: 'b',
            c: 'c',
            length: 3,
            [Symbol.iterator]: Array.prototype[Symbol.iterator]
        };
        for (let item of iterable2) {
            console.log(item); // undefined, undefined, undefined
        }
    }
    {
        /**
         * 调用Iterator接口的场合
         * 有一些场合会默认调用Iterator接口(即Symbol.iterator方法)，除了下文会介绍的for...of循环，还有几个别的场合
         * 1、解构赋值：对数组和Set结构进行解构赋值时，会默认调用Symbol.iterator方法
         */
        /*1、解构赋值：对数组和Set结构进行解构赋值时，会默认调用Symbol.iterator方法*/
        let set = new Set().add('a').add('b').add('c');
        let [x, y] = set;
        console.log(x, y); // x='a'; y='b'

        let [first, ...rest] = set;
        console.log(first, rest); // first='a'; rest=['b','c'];

        /*2、扩展运算符*/
        const str = 'hello';
        console.log([...str]); // ["h", "e", "l", "l", "o"]

        let arr = ['b', 'c'];
        console.log(['a', ...arr, 'd']); // ["a", "b", "c", "d"]

        /*3、yield*：后面跟的是一个可遍历的结构，它会调用该结构的遍历器接口 */
        let generator = function* () {
            yield 1;
            yield* [2, 3, 4];
            yield 5;
        };
        const iterator = generator();
        console.log(iterator.next()); // {value: 1, done: false}
        console.log(iterator.next()); // {value: 2, done: false}
        console.log(iterator.next()); // {value: 3, done: false}
        console.log(iterator.next()); // {value: 4, done: false}
        console.log(iterator.next()); // {value: 5, done: false}
        console.log(iterator.next()); // {value: undefined, done: true}

        /*4、其他场合
        for...of
        Array.from()
        Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
        Promise.all()
        Promise.race()*/
    }
    {
        /**
         * 字符串的Iterator接口：字符串是一个类似数组的对象，也原生具有Iterator接口
         */
        const someString = "hi";
        console.log(typeof someString[Symbol.iterator]); // function

        let iterator = someString[Symbol.iterator]();
        console.log(iterator.next()); // {value: "h", done: false}
        console.log(iterator.next()); // {value: "i", done: false}
        console.log(iterator.next()); // {value: undefined, done: true}

        let str = String("hi");
        console.log([...str]); // ["h", "i"]
    }
    {
        /**Iterator接口与Generator函数*/
        const myIterable = {};
        myIterable[Symbol.iterator] = function* () {
            yield 1;
            yield 2;
            yield 3;
        };
        console.log([...myIterable]); // [1, 2, 3]

        /*或者采用下面的简洁写法*/
        let obj = {
            * [Symbol.iterator]() {
                yield 'hello';
                yield 'world';
            }
        };

        for (let x of obj) {
            console.log(x); // hello world
        }
    }
    {
        /**
         * 遍历器对象的return()，throw()
         * return()：return方法的使用场合是，如果for...of循环提前退出(通常是因为出错，或者有break语句或continue语句)，就会调用return方法。如果一个对象在完成遍历前，需要清理或释放资源，就可以部署return方法
         * throw()：主要是配合Generator函数使用，一般的遍历器对象用不到这个方法
         */
        /*function readLinesSync(file) {
            return {
                next() {
                    if (file.isAtEndOfFile()) {
                        file.close();
                        return { done: true };
                    }
                },
                return() {
                    file.close();
                    return { done: true };
                },
            };
        }
        for (let line of readLinesSync(fileName)) {
            console.log(line);
            break;
        }*/
    }
    {
        /**
         * for...of循环
         * 使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如arguments对象、DOM NodeList 对象)、后文的 Generator 对象，以及字符串
         */
    }
    {
        /** 数组 */
        const arr = ['red', 'green', 'blue'];
        for (const string of arr) {
            console.log(string); // red green blue
        }

        const obj = {};
        obj[Symbol.iterator] = arr[Symbol.iterator].bind(arr);
        for (const objElement of obj) {
            console.log(objElement); // red green blue
        }

        /*for...of循环可以代替数组实例的forEach方法，允许遍历获得键值*/
        arr.forEach(function (element, index) {
            console.log(element); // red green blue
            console.log(index);   // 0 1 2
        });

        /*for...in循环读取键名，for...of循环读取键值*/
        const arr2 = ['a', 'b', 'c', 'd'];
        for (const string of arr2) {
            console.log(string); // a b c d
        }
        for (const key in arr2) {
            if (arr2.hasOwnProperty(key)) {
                console.log(key); // 0 1 2 3
            }
        }

        /*for...of循环调用遍历器接口，数组的遍历器接口只返回具有数字索引的属性。这一点跟for...in循环也不一样*/
        let arr3 = [3, 5, 7];
        arr3.foo = 'hello';
        for (const key in arr3) {
            console.log(key); // 0 1 2 foo
            console.log(arr3[key]); // 3 5 7 hello
        }
        for (let i of arr3) {
            console.log(i); //  "3", "5", "7"  for...of循环不会返回数组arr的foo属性
        }
    }
    {
        /**
         * Set和Map结构
         * 原生具有Iterator接口，可以直接使用for...of循环
         */
        /*首先，遍历的顺序是按照各个成员被添加进数据结构的顺序。
        其次，Set结构遍历时，返回的是一个值，而Map结构遍历时，返回的是一个数组，该数组的两个成员分别为当前Map成员的键名和键值*/
        let engines = new Set(["Gecko", "Trident", "Webkit", "Webkit"]);
        for (const e of engines) {
            console.log(e); // Gecko Trident Webkit
        }

        let map = new Map().set('edition', 6).set("committee", "TC39").set("standard", "ECMA-262");
        for (const [key, value] of map) {
            console.log(key + " : " + value); // edition : 6  committee : TC39  standard : ECMA-262
        }

        let m = new Map().set('a', 1).set('b', 2);
        for (let pair of m) {
            console.log(pair); // ["a", 1] ["b", 2]
        }
        for (let [key, value] of m) {
            console.log(key + ' : ' + value); // a : 1  b : 2
        }
    }
    {
        /**计算生成的数据结构*/
        let arr = ['a', 'b', 'c'];
        for (let pair of arr.entries()) {
            console.log(pair); // [0, "a"] [1, "b"] [2, "c"]
        }
    }
    {
        /**
         * 类似数组的对象
         * 类似数组的对象包括好几类。下面是for...of循环用于字符串、DOM NodeList对象、arguments对象的例子
         */
        /*字符串*/
        let str = "hello";
        for (let s of str) {
            console.log(s); // h e l l o
        }

        /*DOM NodeList对象*/
        let paras = document.querySelectorAll("p");
        for (let p of paras) {
            p.classList.add("test");
        }

        /*arguments对象*/
        function printArgs() {
            for (let x of arguments) {
                console.log(x);
            }
        }

        printArgs('a', 'b'); // 'a' 'b'

        /*对于字符串来说，for...of循环还有一个特点，就是会正确识别32位UTF-16字符*/
        for (let x of 'a\uD83D\uDC0A') {
            console.log(x); // 'a' '\uD83D\uDC0A'
        }

        /*并不是所有类似数组的对象都具有iterator接口，一个简便的解决方法，就是使用Array.from方法将其转为数组*/
        let arrayLike = {length: 2, 0: 'a', 1: 'b'};
        for (let x of Array.from(arrayLike)) {
            console.log(x); // a b
        }
        for (let x in Array.from(arrayLike)) {
            console.log(arrayLike[x]); // a b
        }
    }
    {
        /**
         * 对象：对于普通的对象，for...of结构不能直接使用，会报错，必须部署了iterator接口后才能使用。但是，这样情况下，for...in循环依然可以用来遍历键名
         */
        let es6 = {
            edition: 6,
            committee: "TC39",
            standard: "ECMA-262"
        };
        for (const key in es6) {
            console.log(key + ": " + es6[key]); // edition: 6  committee: TC39  standard: ECMA-262
        }
        /*for (const key of es6) {
            console.log(key); // Uncaught TypeError: es6 is not iterable
        }*/

        /*一种解决方法是，使用Object.keys方法将对象的键名生成一个数组，然后遍历这个数组*/
        for (const key of Object.keys(es6)) {
            console.log(key + ": " + es6[key]); // edition: 6  committee: TC39  standard: ECMA-262
        }

        /*另一个方法是使用Generator函数将对象重新包装一下*/
        function* entries(obj) {
            for (let key of Object.keys(obj)) {
                yield [key, obj[key]];
            }
        }

        for (let [key, value] of entries(es6)) {
            console.log(key, "->", value); // edition -> 6  committee -> TC39  standard -> ECMA-262
        }
    }
    {
        /**与其他遍历语法的比较*/
        /*1、for循环*/
        let myArray = [1, 2, 3, 4, 5, 6];
        for (let index = 0; index < myArray.length; index++) {
            console.log(myArray[index]); // 1 2 3 4 5 6
        }
        /*这种写法比较麻烦，因此数组提供内置的forEach方法*/
        myArray.forEach(function (value) {
            console.log(value); // 1 2 3 4 5 6
        });
        /*这种写法的问题在于，无法中途跳出forEach循环，break命令或return命令都不能奏效。*/

        /*2、for...in循环可以遍历数组的键名*/
        for (const index in myArray) {
            console.log(myArray[index]); // 1 2 3 4 5 6
        }
        /*for...in循环有几个缺点
        * 数组的键名是数字，但是for...in循环是以字符串作为键名“0”、“1”、“2”等等
        * for...in循环不仅遍历数字键名，还会遍历手动添加的其他键，甚至包括原型链上的键
        * 某些情况下，for...in循环会以任意顺序遍历键名*/
        /*for...in循环主要是为遍历对象而设计的，不适用于遍历数组*/

        /*3、for...of循环相比上面几种做法，有一些显著的优点*/
        for (let value of myArray) {
            console.log(value); // 1 2 3 4 5 6
        }
        /*有着同for...in一样的简洁语法，但是没有for...in那些缺点
        * 不同用于forEach方法，它可以与break、continue和return配合使用
        * 提供了遍历所有数据结构的统一操作接口*/

        /*下面是一个使用break语句，跳出for...of循环的例子*/
        let fibonacci = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368];
        for (const n of fibonacci) {
            if (n > 1000)
                break;
            console.log(n); // 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
        }
    }
</script>
</body>
</html>